﻿/*
 * LDAPAttributeStore
 * Version : 1.1
 * Author : Olivier DETILLEUX
 * Edited : Olivier DETILLEUX
 * Date : 23/10/2013
 * 
 * Description : 
 *      This project allows you to query an LDAP Directory that does not support Windows Authentication.
 *      Tested with AD LDS and Sun One.
 * 
 * Version Reviews : 
 *      0.1 : Creation of the project
 *      1.0 : First publication of the project
 *      2.0 :   Remove the 1 to many result.
 *              Bug fixes
 *      
 * */

using System;
using System.Collections.Generic;
using System.Net.Security;
using System.ServiceModel;
using Microsoft.IdentityModel.Threading;
using Microsoft.IdentityServer.ClaimsPolicy.Engine.AttributeStore;
using System.DirectoryServices;
using System.DirectoryServices.Protocols;

namespace LDAPAttributeStore
{
    public class LDAPAttributeStore : IAttributeStore
    {
        #region IAttributeStore Members
        /* 
         * Optionnal Initialization Parameters : 
         _servername : LDAP server
         _port : LDAP port
         _username : distinguished name of an user that has read write in the LDAP directory
         _password : password of _username
         _secured : if true, use ldaps. Else use ldap
         _root : the node where the search will start
         */

        string _servername, _port, _username, _password, _secured, _root;
        /* The format of the query is : 
           ldadFilter;attributeList
        */
        string ldapFilter; string[] attributeList;

        public IAsyncResult BeginExecuteQuery(string query, string[] parameters, AsyncCallback callback, object state)
        {
            // Elements of the query are separated with ;
            Char[] querySplitChar = new Char[] { ';' };

            // attributes in the query are separated with ,
            Char[] attributeSplitChar = new Char[] { ',' };

            if (String.IsNullOrEmpty(query))
            {
                throw new AttributeStoreQueryFormatException("No query string.");
            }

            if (query.Split(querySplitChar).Length != 2)
            {
                throw new AttributeStoreQueryFormatException("Invalid query string.");
            }

            // first part of the query is the LDAPFilter
            ldapFilter = query.Split(querySplitChar)[0];
            if (ldapFilter == null)
            {
                throw new AttributeStoreQueryFormatException("Query attribute cannot be null.");
            }

            // second part of the query is the list of attributes
            attributeList = query.Split(querySplitChar)[1].Split(attributeSplitChar);
            if (attributeList == null)
            {
                throw new AttributeStoreQueryFormatException("Query attribute list cannot be null.");
            }

            if (parameters == null)
            {
                throw new AttributeStoreQueryFormatException("No query parameter.");
            }

            if (parameters.Length != 1)
            {
                throw new AttributeStoreQueryFormatException("More than one query parameter.");
            }

            // Get the parameter for the query
            string inputString = parameters[0];

            if (inputString == null)
            {
                throw new AttributeStoreQueryFormatException("Query parameter cannot be null.");
            }

            string result = null;

            string ldapPath = string.Format("LDAP://{0}:{1}/{2}", _servername, _port, _root);

            // the ldapFilter will contain {0} which will get populated by the parameters[0] by the string.Format method
            string ldapquery = string.Format(ldapFilter.Trim(), parameters[0]);

            AuthenticationTypes authenticationType;

            // find the required authentication type
            switch (_secured)
            {
                case "true":
                    {
                        authenticationType = AuthenticationTypes.SecureSocketsLayer;
                        break;
                    }
                case "false":
                    {
                        authenticationType = AuthenticationTypes.None;
                        break;
                    }
                default:
                    {
                        throw new AttributeStoreInvalidConfigurationException("The secured parameter value is not supported.");
                    }
            }

            // New DirectoryEntry, DirectorySearcher and SearchResultCollection
            DirectoryEntry ldapDirectory; DirectorySearcher ldapDirectorySearcher; 

            //SearchResultCollection ldapSearchResult;
            SearchResult ldapSearchResult;

            try
            {
                ldapDirectory = new DirectoryEntry(ldapPath, _username, _password, authenticationType);
                ldapDirectorySearcher = new DirectorySearcher(ldapDirectory, ldapquery);
                ldapSearchResult = ldapDirectorySearcher.FindOne();
            }
            catch (Exception ex)
            {
                throw new AttributeStoreQueryExecutionException(ex.Message);
            }
            
            int column = 0;
            string[][] outputValues = new string[1][];
            outputValues[0] = new string[attributeList.Length];

            if (ldapSearchResult != null)
            {

                foreach (string attribute in attributeList)
                {
                    if (ldapSearchResult.Properties.Contains(attribute.Trim()))
                    {
                        result = ldapSearchResult.Properties[attribute.Trim()][0].ToString();
                        outputValues[0][column] = result;

                    }

                    column++;

                }
            }
            
            ldapDirectory.Dispose();
            
            // Return the output array
            TypedAsyncResult<string[][]> asyncResult = new TypedAsyncResult<string[][]>(callback, state);
            asyncResult.Complete(outputValues, true);
            return asyncResult;
        }

        public string[][] EndExecuteQuery(IAsyncResult result)
        {
            return TypedAsyncResult<string[][]>.End(result);
        }

        /*
         * Initialization of the optional configuration parameters
         * */

        public void Initialize(Dictionary<string, string> config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }

            if (!config.TryGetValue("servername", out _servername))
            {
                throw new AttributeStoreInvalidConfigurationException("ServerName configuration entry not found");
            }

            if (string.IsNullOrEmpty(_servername))
            {
                throw new AttributeStoreInvalidConfigurationException("ServerName should be valid");
            }

            if (!config.TryGetValue("port", out _port))
            {
                throw new AttributeStoreInvalidConfigurationException("Port configuration entry not found");
            }

            if (string.IsNullOrEmpty(_port))
            {
                throw new AttributeStoreInvalidConfigurationException("Port should be valid");
            }

            if (!config.TryGetValue("username", out _username))
            {
                throw new AttributeStoreInvalidConfigurationException("Port configuration entry not found");
            }

            if (string.IsNullOrEmpty(_username))
            {
                throw new AttributeStoreInvalidConfigurationException("Port should be valid");
            }

            if (!config.TryGetValue("password", out _password))
            {
                throw new AttributeStoreInvalidConfigurationException("Password configuration entry not found");
            }

            if (string.IsNullOrEmpty(_password))
            {
                throw new AttributeStoreInvalidConfigurationException("Password should be valid");
            }
            else
            {
                _password = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(_password.Trim()));
            }

            if (!config.TryGetValue("secured", out _secured))
            {
                throw new AttributeStoreInvalidConfigurationException("Secured configuration entry not found");
            }

            if (string.IsNullOrEmpty(_secured))
            {
                throw new AttributeStoreInvalidConfigurationException("Secured should be valid");
            }

            if (!config.TryGetValue("root", out _root))
            {
                _root = "";
            }

            if (string.IsNullOrEmpty(_root))
            {
                _root = "";
            }
        }

        #endregion
    }
}
